package repository

// This file contains template for generate db
// repository access code, it may have some error
// to compile (just ignore them).
import (
	"fmt"
	"happy-sudoku/internal/codec"

	"github.com/jinzhu/gorm"
	"github.com/pkg/errors"
)

// IObjNameRepo is to be implement by derived Repo
type IObjNameRepo interface {
	Exist(id int) bool
	GetAll() ([]*Object, error)
	GetList(ids []int) ([]*Object, error)
	GetOne(id int) (*Object, error)
	GetOneByQuery(query interface{}, args ...interface{}) (*Object, error)
	Create(Object) (*Object, error)
	Update(Object) (*Object, error)
	Delete(id int) error
}

const (
	// ObjNameErrHeader represents the errHeader of ObjName
	ObjNameErrHeader = "ObjName."
)

// ObjNameRepo abstract Object data access
type ObjNameRepo struct {
	db *gorm.DB
}

// NewObjNameRepo creates ObjectRepo
func NewObjNameRepo(db *gorm.DB) *ObjNameRepo {
	return &ObjNameRepo{db: db}
}

// Exist returns whether ObjName exist
func (r *ObjNameRepo) Exist(id int) bool {
	_, err := r.GetOne(id)
	return err == nil
}

// Create creates Object by args in db
func (r *ObjNameRepo) Create(obj Object) (*Object, error) {
	const errHeader = ObjNameErrHeader + "Create"

	// set Object ID to 0, let db auto increment
	obj.ID = 0
	res := r.db.Save(&obj)
	if res.Error != nil {
		return nil, errors.Wrap(res.Error, errHeader)
	}
	return &obj, nil
}

// GetOne find Object by id
func (r *ObjNameRepo) GetOne(id int) (*Object, error) {
	const errHeader = ObjNameErrHeader + "GetOne"

	object := PackageName.NewObjName()
	res := r.db.Where("id = ?", id).Take(&object)
	if gorm.IsRecordNotFoundError(res.Error) {
		return nil, codec.ErrObjName404.WithField("id", id)
	}

	if res.Error != nil {
		return nil, errors.Wrap(res.Error, errHeader)
	}
	return object, nil
}

// GetOneByQuery gets ObjName by Query string (in gorm)
func (r *ObjNameRepo) GetOneByQuery(query interface{}, args ...interface{}) (*Object, error) {
	const errHeader = ObjNameErrHeader + "GetOneByQuery"

	object := PackageName.NewObjName()
	// !! random() select one object randomly, only use in sqlite3 or postgresSQL
	res := r.db.Where(query, args...).Order(gorm.Expr("random()")).Take(&object)
	if gorm.IsRecordNotFoundError(res.Error) {
		return nil, codec.ErrObjName404.WithFields(codec.CodeFields{
			"query": query,
			"args":  args,
		})
	}

	if res.Error != nil {
		return nil, errors.Wrap(res.Error, errHeader)
	}
	return object, nil
}

// GetList find List of Object by id
func (r *ObjNameRepo) GetList(ids []int) ([]*Object, error) {
	const errHeader = ObjNameErrHeader + "GetList"

	results := []*Object{}
	res := r.db.Where(ids).Find(&results)
	if gorm.IsRecordNotFoundError(res.Error) {
		// ignore not found error
		// !! Log
		return results, nil
	}

	if res.Error != nil {
		return nil, errors.Wrap(res.Error, errHeader)
	}
	return results, res.Error
}

// GetAll find all objects
func (r *ObjNameRepo) GetAll() ([]*Object, error) {
	const errHeader = ObjNameErrHeader + "GetAll"

	results := []*Object{}
	res := r.db.Find(&results)

	if res.Error != nil {
		return nil, errors.Wrap(res.Error, errHeader)
	}
	return results, nil
}

// Update update already exist field
func (r *ObjNameRepo) Update(obj Object) (*Object, error) {
	const errHeader = ObjNameErrHeader + "Update"

	// ID should not equal to zero
	if obj.ID == 0 {
		return nil, errors.Wrap(fmt.Errorf("id shouln't be zero"), errHeader)
	}
	// reset id
	res := r.db.Save(&obj)
	if res.Error != nil {
		return nil, errors.Wrap(res.Error, errHeader)
	}
	return &obj, nil
}

// Delete deletes ObjectName already existed
func (r *ObjNameRepo) Delete(id int) error {
	const errHeader = ObjNameErrHeader + "Delete"

	if id == 0 {
		return errors.Wrap(errors.New("id shouldn't be zero"), errHeader)
	}
	res := r.db.Where("id = ?", id).Delete(&Object{})

	if res.Error != nil {
		return errors.Wrap(res.Error, errHeader)
	}
	return nil
}
